﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using fastJSON;

namespace Less.Util
{
    public static class Extensions
    {
        #region Object
        /// <summary>
        /// Converts my value to another type
        /// </summary>
        /// <typeparam name="T">The target type expected</typeparam>
        /// <param name="me">My value</param>
        /// <param name="returnDefaultWhenException">A flag to return defalt values or exceptions. true=defaults :-)</param>
        /// <returns>The value converted</returns>
        public static T ConvertTo<T>(this object me, bool returnDefaultWhenException = true)
        {
            Type targetType = typeof (T);
            Type sourceType = (me == null) ? typeof (object) : me.GetType();
            try
            {
                if (targetType.IsEnum)
                {
                    return (T) Enum.Parse(targetType, me.ToString());
                }
                else
                {
                    Delegate converter = BuildConversion(sourceType, targetType);
                    return (T) converter.DynamicInvoke(me);
                }
            }
            catch (Exception)
            {
                if (returnDefaultWhenException)
                {
                    return default(T);
                }
                else
                {
                    throw;
                }
            }
        }
        /// <summary>
        /// A delegated created for concrete conversion cases
        /// </summary>
        /// <param name="fromType">Original type</param>
        /// <param name="toType">Target Type</param>
        /// <returns>A conversion method</returns>
        private static Delegate BuildConversion(Type fromType, Type toType)
        {
            ParameterExpression input = Expression.Parameter(fromType, "input");
            Expression body;
            try
            {
                body = Expression.Convert(input, toType);
            }
            catch (InvalidOperationException)
            {
                ConstantExpression conversionType = Expression.Constant(toType);
                body = Expression.Call(typeof (Convert), "ChangeType", null, input, conversionType);
            }
            LambdaExpression result = Expression.Lambda(body, input);
            return result.Compile();
        }
        /// <summary>
        /// Serializes an object to JSON string notation, and optinal writes ti to a file
        /// </summary>
        /// <param name="me">The object that will be serialized</param>
        /// <param name="filePath">The full path name where saves the serailization. Default is null for not saving</param>
        /// <returns>A string with the JSON representation</returns>
        public static string Serialize(this object me, string filePath = null)
        {
            if (filePath == null)
                return Functions.Serialize(me);
            else
                return Functions.Serialize(me, filePath);
        }
    
        /// <summary>
        /// Deserializes a string with a JSON representation of an object into an instance of that object
        /// </summary>
        /// <typeparam name="T">The object type</typeparam>
        /// <param name="me">The string with the JSON</param>
        /// <returns>The object instance</returns>
        public static T Deserialize<T>(this string me)
        {
            if (me.HasValue())
            {
                return JSON.Instance.ToObject<T>(me);
            }
            else
            {
                return default(T);
            }
        }
        #endregion
        #region String
        /// <summary>
        /// Determines whether the specified string is null or empty.
        /// </summary>
        /// <param name = "me">The string to check.</param>
        /// /// <returns>
        /// <c>true</c> if this string doesn't have value; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsNullOrEmpty(this string me)
        {
            return me == null || String.IsNullOrEmpty(me.Trim());
        }
        /// <summary>
        /// Determines whether a string is not null nor empty.
        /// </summary>
        /// <param name="me">The string to check</param>
        /// <returns>
        /// <c>true</c> if this string has value; otherwise, <c>false</c>.
        /// </returns>
        public static bool HasValue(this string me)
        {
            return !me.IsNullOrEmpty();
        }
        /// <summary>
        /// Returns the left part of the string, or the full string if it is smaller
        /// </summary>
        /// <param name="me">The original string.</param>
        /// <param name="characterCount">The character count to be returned.</param>
        /// <returns>The left part</returns>
        public static string Left(this string me, int characterCount)
        {
            if (me.Length >= characterCount)
            {
                return me.Substring(0, characterCount);
            }
            else
            {
                return me;
            }
        }
        /// <summary>
        /// Returns the Right part of the string, or the full string if it is smaller
        /// </summary>
        /// <param name="me">The original string.</param>
        /// <param name="characterCount">The character count to be returned.</param>
        /// <returns>The right part</returns>
        public static string Right(this string me, int characterCount)
        {
            if (me.Length >= characterCount)
            {
                return me.Substring(me.Length - characterCount);
            }
            else
            {
                return me;
            }
        }
        /// <summary>
        /// Returns a fixed number of chars from the the start, paddig with balnks whe is smaller
        /// <remarks>Usefull for formatting </remarks>
        /// </summary>
        /// <param name="me">The original string.</param>
        /// <param name="characterCount">The character count to be returned.</param>
        /// <returns>The left part, padded with blanks</returns>
        public static string FixedLenght(this string me, int characterCount)
        {
            return me.Left(characterCount).PadRight(characterCount);
        }
        /// <summary>
        /// Tests whether the contents of a string is a numeric value
        /// </summary>
        /// <param name = "me">String to check</param>
        /// <returns>
        /// Boolean indicating whether or not the string contents are numeric
        /// </returns>
        /// <remarks>
        /// Contributed by Kenneth Scott
        /// </remarks>
        public static bool IsNumeric(this string me)
        {
            float output;
            return Single.TryParse(me, out output);
        }
        #endregion
        #region IList
        /// <summary>
        /// Determines whether a collection is null or has no elements without having to enumerate the entire collection to get a count.  Uses LINQ.
        /// </summary>
        /// <typeparam name="T">The item type.</typeparam>
        /// <param name="me">The items.</param>
        /// <returns>
        /// <c>true</c> if this list is null or empty; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsNullOrEmpty<T>(this IList<T> me)
        {
            return me == null || !me.Any();
        }

        /// <summary>
        /// Determines whether a collection is null or has no elements without having to enumerate the entire collection to get a count.  Uses LINQ.
        /// </summary>
        /// <typeparam name="T">The item type.</typeparam>
        /// <param name="me">The items.</param>
        /// <returns>
        /// <c>true</c> if this list has value; otherwise, <c>false</c>.
        /// </returns>
        public static bool HasValue<T>(this IList<T> me)
        {
            return me != null && me.Any();
        }
        #endregion
        #region Exception
        /// <summary>
        /// Overrides the default representation of an Exception
        /// </summary>
        /// <param name="me">The exception to create the string from.</param>
        /// <returns></returns>
        public static string ToLogString(this Exception me)
        {
            if (me != null)
            {
                int level = 0;
                const string tab = " := "; // in notepad++ with language smalltalk displays in red
                Exception currentException = me;
                string exceptionType = currentException.GetType() + ":";
                var msg = new StringBuilder(exceptionType);
                msg.Append(tab);
                string baseExceptionType = currentException.GetBaseException().GetType() + ":";
                if (baseExceptionType.HasValue() && exceptionType != baseExceptionType && baseExceptionType != "Exception:")
                {
                    msg.Append(baseExceptionType);
                    msg.Append(tab);
                }
                try
                {
                    while (currentException != null)
                    {
                        if (level > 0)
                        {
                            msg.Append(" lvl:" + level);
                        }
                        msg.Append(currentException.Message);
                        if (me.StackTrace.HasValue())
                        {
                            msg.Append(tab);
                            msg.Append("Stk:");
                            msg.Append(ShortStackTrace(me.StackTrace));
                        }
                        if (me.Source.HasValue())
                        {
                            msg.Append(tab);
                            msg.Append("Src:");
                            msg.Append(me.Source);
                        }
                        currentException = currentException.InnerException;
                        level++;
                    }
                }
                finally
                {
                }
                return msg.ToString();
            }
            else
            {
                return "";
            }
        }
    
    
        /// <summary>
        /// Format to get less verbose logs
        /// </summary>
        /// <param name="me"></param>
        /// <returns></returns>
        private static string ShortStackTrace(string me)
        {
            //-    en Test.LessFramework.LogConfig() en C:\Users\Alberto Basalo\Dropbox\Work\Code\LessFramework\Test\LessFramework.cs:línea 53
            int init = me.IndexOf(@"C:\");
            int end = me.IndexOf(@".cs");
            if (init == -1 || end == -1)
            {
                return me;
            }
            string pathName = me.Substring(init, end - init);
            string dirName = Path.GetDirectoryName(pathName);
            me = me.Replace(dirName, "");
            //-    en Test.LessFramework.LogConfig() en \LessFramework.cs:línea 53
            return me;
        }
        #endregion
        #region MethodInfo
        public static string ToFriendlyName(this MethodInfo me)
        {
            return Functions.GetFriendlyName(me);
        }
        #endregion
        // Inspiration:
        //http://dnpextensions.codeplex.com/
        //http://www.extensionmethod.net/
    }
}